package lpg;

import java.util.ArrayList;
import java.util.List;

class Parser extends Stacks
{
    Parser(Control control, LexStream lexStream,
            LookupTable<String, VariableSymbol> variableTable, LookupTable<String, MacroSymbol> macroTable)
    {
        this.control        = control;
        this.option         = control.option;
        this.lexStream      = lexStream;
        this.variableTable  = variableTable;
        this.macroTable     = macroTable;

        this.lpgact         = new Lpgact(this);
    }

    private int tAction(int act, int sym, LexStream stream)
    {
        act = lpgprs.tAction(act, sym);
        if (act > Lpgprs.LA_STATE_OFFSET)
        {
            for (int tok = stream.peek(); ; tok = stream.next(tok))
            {
               act = lpgprs.lookAhead(act - Lpgprs.LA_STATE_OFFSET, stream.kind(tok));
               if (act <= Lpgprs.LA_STATE_OFFSET)
                   break;
            }
        }
        return act;
    }

    void parse()
    {
        parse(0);
    }

    void parse(int startIndex)
    {
        lexStream.reset(startIndex);

        curTok = lexStream.getToken();
        int act = Lpgprs.START_STATE;
        int currentKind = lexStream.kind(curTok);

        //
        // Start parsing.
        //
        stateStackTop = -1;

        //
        // Process a terminal
        //
        while (true)
        {
            if (++stateStackTop >= stateStack.length)
                reallocateStacks();

            stateStack[stateStackTop] = act;
            locationStack[stateStackTop] = curTok;

            act = tAction(act, currentKind, lexStream);

            if (act <= Lpgprs.NUM_RULES)
            {
                stateStackTop--; // make reduction look like a shift-reduce
            }
            else if (act > Lpgprs.ERROR_ACTION)
            {
                curTok = lexStream.getToken();
                currentKind = lexStream.kind(curTok);

                act -= Lpgprs.ERROR_ACTION;
            }
            else if (act < Lpgprs.ACCEPT_ACTION)
            {
                curTok = lexStream.getToken();
                currentKind = lexStream.kind(curTok);

                continue;
            }
            else
                break;

            //
            // Process a non_terminal
            //
            do
            {
                stateStackTop -= (Lpgprs.rhs[act] - 1);
                lpgact.ruleAction(act);
                act = lpgprs.ntAction(stateStack[stateStackTop], Lpgprs.lhs[act]);
            }
            while (act <= Lpgprs.NUM_RULES);
        } // process_termainal

        if (act == Lpgprs.ERROR_ACTION)
        {
            //
            // Normally, we would want to do this:
            //
            //    DiagnoseParser diagnoseParser = new DiagnoseParser(lexStream);
            //
            String msg;
            int prevTok = lexStream.previous(curTok);
            int prevKind = lexStream.kind(prevTok);
            if (prevKind == Lpgprs.TK_NOTICE_KEY ||
                prevKind == Lpgprs.TK_GLOBALS_KEY ||
                prevKind == Lpgprs.TK_AST_KEY  ||
                prevKind == Lpgprs.TK_HEADERS_KEY)
            {
                 BlockSymbol block = option.defaultBlock();
                 msg = "A block " + block.blockBegin() + "  ...  " + block.blockEnd() +
                        " is expected here instead of this token";
            }
            else
                msg = "Syntax error detected on this token";

            option.emitError(curTok, msg);
            control.exit();
        }

    }

    void addVariableName(int index)
    {
        String variableName = lexStream.nameString(index).substring(1); // +1 to skip the Escape symbol
        VariableSymbol variableSymbol = variableTable.findOrInsert(
                variableName, new VariableSymbol(variableName, variableTable.size()));

        lexStream.getTokenReference(index).setSymbol(variableSymbol);
    }

    void setIdentifierIndex(int index)
    {
        if (identifierIndex == 0)
            identifierIndex = index;
        else
            option.emitWarning(index, ActError.RESPECIFICATION_OF_IDENTIFIER_SYMBOL.msg);
    }

    void setEolIndex(int index)
    {
        if (eolIndex == 0)
            eolIndex = index;
        else
            option.emitWarning(index, ActError.RESPECIFICATION_OF_EOL_SYMBOL.msg);
    }

    void setEofIndex(int index)
    {
        if (eofIndex == 0)
            eofIndex = index;
        else
            option.emitWarning(index, ActError.RESPECIFICATION_OF_EOF_SYMBOL.msg);
    }

    void setErrorIndex(int index)
    {
        if (errorIndex == 0)
            errorIndex = index;
        else
            option.emitWarning(index, ActError.RESPECIFICATION_OF_ERROR_SYMBOL.msg);
    }

    //
    // Compare the right hand-side of two rules and return true if they are identical.
    //
    private boolean compare(RuleDefinition rule1, RuleDefinition rule2)
    {
        //
        // Copy all symbols in the right-hand side of rule 1 to list1
        //
        IntArrayList list1 = new IntArrayList();
        for (int i = lexStream.next(rule1.separatorIndex); i < rule1.endRhsIndex; i++)
        {
            if (lexStream.kind(i) == Lpgprs.TK_SYMBOL)
                list1.add(i);
        }

        //
        // Copy all symbols in the right-hand side of rule 2 to list2
        //
        IntArrayList list2 = new IntArrayList();
        for (int i = lexStream.next(rule2.separatorIndex); i < rule2.endRhsIndex; i++)
        {
            if (lexStream.kind(i) == Lpgprs.TK_SYMBOL)
                list2.add(i);
        }

        //
        // Compare symbols in list1 to symbols in list2 and return true if they are the same.
        //
        int size = list1.size();
        if (size == list2.size())
        {
            for (int i = 0; i < size; i++)
            {
                if (!lexStream.nameString(list1.get(i)).equals(lexStream.nameString(list2.get(i))))
                    return false;
            }
            return true;
        }

        return false;
    }

    //
    // Merge the information from an imported grammar with the
    // information for this grammar.
    //
    void merge(int importFileIndex, Parser imp)
    {
        //
        // Process drop list before adding rules
        //
        LookupTable<String, RuleSymbol> symbolTable = new LookupTable<String, RuleSymbol>();
        LookupTable<String, RuleSymbol> ruleTable = new LookupTable<String, RuleSymbol>();
        for (int i = 0, n = droppedRules.size(); i < n; i++)
        {
            //
            // If the separator index is 0, it is an indication
            // that all rules with this lhs_index should be dropped.
            //
            RuleDefinition ruleDef = droppedRules.get(i);
            String ruleName = lexStream.nameString(ruleDef.lhsIndex);
            if (ruleDef.separatorIndex == 0)
            {
                symbolTable.findOrInsert(ruleName, new RuleSymbol(ruleName, symbolTable.size()));
            }
            else
            {
                RuleSymbol ruleSymbol = ruleTable.findOrInsert(
                        ruleName, new RuleSymbol(ruleName, ruleTable.size()));
                ruleSymbol.addRule(i);
            }
        }

        //
        // Process special symbols.
        //
        if (!imp.startIndexes.isEmpty())
        {
            ImportedStartIndexes element = new ImportedStartIndexes(importFileIndex);
            element.startIndexes.addAll(imp.startIndexes);
            importedStartIndexes.add(element);
        }

        if (imp.identifierIndex != 0)
            setIdentifierIndex(imp.identifierIndex);
        if (imp.eolIndex != 0)
            setEolIndex(imp.eolIndex);
        if (imp.eofIndex != 0)
            setEofIndex(imp.eofIndex);
        if (imp.errorIndex != 0)
            setErrorIndex(imp.errorIndex);

        terminals.addAll(imp.terminals);
        keywords.addAll(imp.keywords);
        exports.addAll(imp.exports);

        for (int i = 0, n = imp.aliases.size(); i < n; i++)
        {
            //
            // Allow aliasing only for names that have not been dropped.
            //
            AliasDefinition aliasDef = imp.aliases.get(i);
            if (symbolTable.find(lexStream.nameString(aliasDef.lhsIndex)) == null)
                aliases.add(aliasDef);
        }

        for (int i = 0, n = imp.names.size(); i < n; i++)
        {
            //
            // Assign name to all symbols that have not been dropped.
            //
            NameDefinition nameDef = imp.names.get(i);
            if (symbolTable.find(lexStream.nameString(nameDef.lhsIndex)) == null)
                names.add(nameDef);
        }

        noticeBlocks.addAll(imp.noticeBlocks);
        globalBlocks.addAll(imp.globalBlocks);
        headerBlocks.addAll(imp.headerBlocks);
        initialBlocks.addAll(imp.initialBlocks);
        trailerBlocks.addAll(imp.trailerBlocks);

        for (int i = 0, n = imp.types.size(); i < n; i++)
        {
            //
            // Add type declaration for all symbols that have not been dropped.
            //
            TypeDefinition typeDef = imp.types.get(i);
            if (symbolTable.find(lexStream.nameString(typeDef.symbolIndex)) == null)
                types.add(typeDef);
        }

        //
        // Add all imported rules that have not been dropped.
        //
        // TODO: NOTE THAT THE "DropActions" feature has not yet been IMPLEMENTED !!!
        //
        for (int i = 0, n = imp.rules.size(); i < n; i++)
        {
            RuleDefinition ruleDef = imp.rules.get(i);
            String ruleName = lexStream.nameString(ruleDef.lhsIndex);
            if (symbolTable.find(ruleName) != null)
                continue;

            RuleSymbol ruleSymbol = ruleTable.find(ruleName);
            if (ruleSymbol == null)
            {
                rules.add(ruleDef);
                continue;
            }

            int k = 0;
            int m = ruleSymbol.rules().size();
            for (; k < m; k++)
            {
                if (compare(droppedRules.get(ruleSymbol.rules().get(k)), ruleDef))
                    break;
            }

            if (k == m) // not a dropped rule
            {
                int precedingRule = rules.size() - 1;

                rules.add(ruleDef);

                //
                // If the first rule in a sequence of rules containing
                // alternations is dropped, we have to replace the alternate
                // symbol by a production symbol in the first alternate rule in
                // the sequence that was not dropped.
                //
                if (lexStream.kind(ruleDef.separatorIndex) == Lpgsym.TK_OR_MARKER &&
                        (precedingRule < 0 || rules.get(precedingRule).lhsIndex != ruleDef.lhsIndex))
                {
                    Token separator = lexStream.getTokenReference(ruleDef.separatorIndex);
                    separator.setKind(lexStream.kind(lexStream.next(ruleDef.lhsIndex)));
                }
            }
        }
    }

    void changeMacroToVariable(int index)
    {
        String variableName = lexStream.nameString(index);

        VariableSymbol variableSymbol = variableTable.find(variableName);
        if (variableSymbol == null)
        {
            variableSymbol = new VariableSymbol(variableName, variableTable.size());
            variableTable.insert(variableName, variableSymbol);
            option.emitWarning(index, ActError.SYMBOL_EXPECTED_INSTEAD_OF_MACRO.msg);
        }

        lexStream.getTokenReference(index).setSymbol(variableSymbol);
    }

    final Control control;
    final Option option;
    final LexStream lexStream;
    final LookupTable<String, VariableSymbol> variableTable;
    final LookupTable<String, MacroSymbol> macroTable;

    int identifierIndex = 0;
    int eolIndex = 0;
    int eofIndex = 0;
    int errorIndex = 0;
    final IntArrayList terminals            = new IntArrayList();
    final IntArrayList keywords             = new IntArrayList();
    final IntArrayList exports              = new IntArrayList();
    final IntArrayList recovers             = new IntArrayList();
    final IntArrayList startIndexes         = new IntArrayList();
    final List<PredecessorSetDefinition> predecessorCandidates = new ArrayList<PredecessorSetDefinition>();
    final List<AliasDefinition> aliases     = new ArrayList<AliasDefinition>();
    final List<NameDefinition> names        = new ArrayList<NameDefinition>();
    final IntArrayList noticeBlocks         = new IntArrayList();
    final IntArrayList globalBlocks         = new IntArrayList();
    final IntArrayList astBlocks            = new IntArrayList();
    final IntArrayList headerBlocks         = new IntArrayList();
    final IntArrayList initialBlocks        = new IntArrayList();
    final IntArrayList trailerBlocks        = new IntArrayList();
    final List<RuleDefinition> rules        = new ArrayList<RuleDefinition>();
    final List<RuleDefinition> droppedRules = new ArrayList<RuleDefinition>();
    final List<RuleDefinition> prostheses   = new ArrayList<RuleDefinition>();
    final List<TypeDefinition> types        = new ArrayList<TypeDefinition>();
    final List<ImportedStartIndexes> importedStartIndexes = new ArrayList<ImportedStartIndexes>();

    final Lpgact lpgact;

    int curTok = 0;

    private static final Lpgprs lpgprs = new Lpgprs();

    static enum ActError
    {
        MACRO_EXPECTED_INSTEAD_OF_SYMBOL("A macro name was expected here instead of a grammar symbol name"),
        SYMBOL_EXPECTED_INSTEAD_OF_MACRO("A grammar symbol name was expected instead of a macro name"),
        RESPECIFICATION_OF_ERROR_SYMBOL("Respecification of the error symbol"),
        RESPECIFICATION_OF_IDENTIFIER_SYMBOL("Respecification of the identifier symbol"),
        RESPECIFICATION_OF_EOL_SYMBOL("Respecification of the eol symbol"),
        RESPECIFICATION_OF_EOF_SYMBOL("Respecification of the eof symbol"),
        RESPECIFICATION_OF_START_SYMBOL("Respecification of the start symbol"),
        RECURSIVE_IMPORT("Attempt to recursively include this file");

        ActError(String msg)
        {
            this.msg = msg;
        }

        final String msg;
    }

}
